﻿#pragma once

#include "CloudMsg.h"
#include <map>
#include <list>
#include <afxmt.h>

#define HEARTBEAT_INTERVAL					(30 * 1000)

#define CloudCallbackReason_Recv			0
#define CloudCallbackReason_Send			1
#define CloudCallbackReason_Error			101
#define CloudCallbackReason_LoginError		102
#define CloudCallbackReason_CloudError		1001

typedef BOOL (WINAPI *LPCLOUD_CALLBACK)(DWORD dwReason, LPVOID lpBuffer, DWORD_PTR lpContext);

/*! 私有云的帮助类，包含一些辅助工具 */
class CPrivateCloudHelper
{
public:
	//! 该结构是回调函数的参数之一。
	typedef struct _CALLBACK_ITEM
	{
		long peer_ip;			/*!< 对方的ip地址，已经转换成网络字节序。 */
		LPCLOUDMSG_HDR hdr;		/*!< 这是一个指针，指向对方返回的消息头。 */
	} CALLBACK_ITEM, *LPCALLBACK_ITEM;

public:
	CPrivateCloudHelper();
	~CPrivateCloudHelper();

	/*! 注册回调函数，该回调函数会在收到网络中其它设备返回的消息时调用。 */
	/*!
	\param lpCallbackRoutine 指向回调函数的地址，由调用者提供。
	\param lpContext 再调用回调函数时，会把该参数传给回调函数，该参数由调用者提供。
	\return 无。
	\sa BeginSearch()
	*/
	VOID RegisterCallback(LPCLOUD_CALLBACK lpCallbackRoutine, DWORD_PTR lpContext);

	/*! 开启一个搜索私有云的线程，在该线程中我们会向整个局域网组播发送查询私有云的请求，并等待回应，在收到回应后调用调用者注册的回调函数。 */
	/*!
	\param lpszCloudId 要搜索的私有云的ID，该参数可选（可以为NULL，为NULL时表示搜索整个局域网里的所有私有云）。
	\return 错误码，同GetLastError()的返回值。
	\sa RegisterCallback()
	*/
	DWORD BeginSearch(LPCTSTR lpszCloudId = NULL);

	/*! 结束上次开启的搜索私有云的线程，请清理相关资源。 */
	/*!
	\param 无。
	\return 无。
	\sa BeginSearch()
	*/
	VOID EndSearch();

	/*! 根据输入的Token Mask和Password生成一个Token。 */
	/*!
	\param lpszTokenMask 第一个Token Mask。
	\param lpszTokenMask2 第二个Token Mask。
	\param lpszPassword 私有云的访问密码。
	\param lpToken 输出参数，保存生成的Token。
	\return 成功则返回TRUE，否则返回FALSE。
	\sa GenerateRandomTokenMask()
	*/
	static BOOL GenerateToken(IN LPTSTR lpszTokenMask, IN LPTSTR lpszTokenMask2, IN LPTSTR lpszPassword, OUT LPBYTE lpToken);

	/*! 生成一个随机的Token Mask。 */
	/*!
	\param lpszTokenMask 输出参数，保存生成的Token Mask。
	\return 成功则返回TRUE，否则返回FALSE。
	\sa GenerateToken()
	*/
	static BOOL GenerateRandomTokenMask(OUT LPTSTR lpszTokenMask);

	/*! 生成CRC循环校验码。 */
	/*!
	\param p 指向要计算校验码的缓冲区的指针。
	\param length 要计算校验码的缓冲区的字节长度。
	\return 返回校验码。
	\sa
	*/
	static int CRC(char *p, int length);

	/*! 对输入的消息进行打包，计算并设置校验码等，所有的消息都必须经过打包才能发送。 */
	/*!
	\param hdr 指向要打包的消息头。
	\return 无。
	\sa
	*/
	static VOID Package(LPCLOUDMSG_HDR hdr);

	/*! 验证消息包是否合法，包括验证CRC。 */
	/*!
	\param hdr 指向要验证的消息头。
	\return 通过返回TRUE，否则返回FALSE。
	\sa
	*/
	static BOOL Verify(LPCLOUDMSG_HDR hdr);

	/*! 验证消息包的头部是否合法，只针对消息头部进行验证，CRC不验证。 */
	/*!
	\param hdr 指向要验证的消息头。
	\return 通过返回TRUE，否则返回FALSE。
	\sa
	*/
	static BOOL VerifyHeader(LPCLOUDMSG_HDR hdr);

private:
	/*! 搜索私有云的线程。 */
	/*!
	\param lpContext
	\return 返回值无意义。
	\sa BeginSearch()
	*/
	static DWORD WINAPI SearchThread(LPVOID lpContext);

private:
	HANDLE m_hThread;
	SOCKET m_hSocket;
	LPCLOUD_CALLBACK m_lpCallbackRoutine;
	DWORD_PTR m_lpCallbackContext;
	TCHAR m_szCloudId[CLOUD_ID_LENGTH];
};

/*! 管理私有云的类 */
class CPrivateCloud
{
	//! 该结构用来管理私有云中和本机连接的其它设备的相关信息。
	typedef struct _PRIVATECLOUD_ITEM
	{
		long ip;								/*!< 对方的ip地址，已经转换成网络字节序。 */
		HANDLE hServerThread;					/*!< TCP服务器线程（和每个设备都会建立两个连接，一个作为服务器，一个作为客户端）。 */
		HANDLE hClientThread;					/*!< TCP客户端线程（和每个设备都会建立两个连接，一个作为服务器，一个作为客户端）。 */
		SOCKET hServerSocket;					/*!< TCP服务器网络套接字。 */
		SOCKET hClientSocket;					/*!< TCP客户端网络套接字。 */
		BOOL bDeviceInformationOK;				/*!< 是否已经获取到对方上报的设备信息。 */
		WCHAR szDeviceId[DEVICE_ID_LENGTH];		/*!< 对方上报的设备ID。 */
		WCHAR szDeviceName[DEVICE_NAME_LENGTH];	/*!< 对方上报的设备名称。 */
		WCHAR szDeviceType[DEVICE_TYPE_LENGTH];	/*!< 对方上报的设备类型。 */
		std::list<LPCLOUDMSG_HDR> cTaskList;	/*!< 等待发送给对方的消息队列。 */
		HANDLE hEvent;							/*!< 通知消息队列有新消息需要发送。 */
		BOOL bTerminated;						/*!< 通知线程退出。 */
		BOOL bConnected;						/*!< 是否已经主动连接对方。 */
	} PRIVATECLOUD_ITEM, *LPPRIVATECLOUD_ITEM;

	//! 该结构用来给线程传递参数。
	typedef struct _CONNECT_CONTEXT
	{
		CPrivateCloud *pCloud;				/*!< 指向CPrivateCloud的指针。 */
		BOOL bRunAsServer;					/*!< 是否作为服务器运行（等待对方主动连接）。 */
		long ip;							/*!< 对方的ip地址（已经转换成网络字节序）。 */
	} CONNECT_CONTEXT, *LPCONNECT_CONTEXT;

public:
	//! 该结构用来给调用者注册的回调函数传递参数。
	typedef struct _CALLBACK_ITEM
	{
		long peer_ip;						/*!< 对方的ip地址（已经转换成网络字节序）。 */
		DWORD_PTR lpData;					/*!< 根据CallbackReason的不同而不同，CallbackReason为CloudCallbackReason_Recv和CloudCallbackReason_Send时为指向CLOUDMSG_HDR的指针。 */
	} CALLBACK_ITEM, *LPCALLBACK_ITEM;

public:
	CPrivateCloud();
	~CPrivateCloud();

	/*! 用来监听网络中UDP组播消息的线程。 */
	/*!
	\param lpContext 指向CPrivateCloud的指针。
	\return 无意义。
	\sa CPrivateCloud
	*/
	static DWORD WINAPI UdpListenThread(LPVOID lpContext);

	/*! 用来监听网络中TCP连接的线程。 */
	/*!
	\param lpContext 指向CPrivateCloud的指针。
	\return 无意义。
	\sa CPrivateCloud
	*/
	static DWORD WINAPI TcpListenThread(LPVOID lpContext);

	/*! 用来处理和私有云中其它设备通信的线程。 */
	/*!
	\param lpContext 指向CPrivateCloud的指针。
	\return 无意义。
	\sa CPrivateCloud
	*/
	static DWORD WINAPI MessageThread(LPVOID lpContext);

	/*! 用来定时发送心跳消息的线程。 */
	/*!
	\param lpContext 指向CPrivateCloud的指针。
	\return 无意义。
	\sa CPrivateCloud
	*/
	static DWORD WINAPI HeartbeatThread(LPVOID lpContext);

	/*! 设置本设备的相关信息，和其他设备交互时发送给对方。 */
	/*!
	\param lpszDeviceId 本设备的ID。
	\param lpszDeviceName 本设备的名称。
	\param lpszDeviceType 本设备的类型。
	\return 无意义。
	\sa
	*/
	VOID SetDeviceInformation(LPCTSTR lpszDeviceId, LPCTSTR lpszDeviceName, LPCTSTR lpszDeviceType);

	/*! 注册回调函数，和其他设备交互时会通过回调函数通知调用者。 */
	/*!
	\param lpCallbackRoutine 回掉函数的地址。
	\param lpContext 调用者传入的回调参数，调用回调函数时会把此参数传递给回调函数。
	\return 无。
	\sa LPCLOUD_CALLBACK
	*/
	VOID RegisterCallback(LPCLOUD_CALLBACK lpCallbackRoutine, DWORD_PTR lpContext);

	/*! 连接到云，连接到云后即可和云中的其他设备建立连接。 */
	/*!
	\param lpszCloudId 私有云的ID。
	\param lpszCloudName 私有云的名称。
	\param lpszPassword 私有云的访问密码。
	\return 同GetLastError。
	\sa
	*/
	DWORD ConnectToCloud(LPTSTR lpszCloudId, LPTSTR lpszCloudName, LPTSTR lpszPassword);

	/*! 是否已经连接到云。 */
	/*!
	\return TRUE表示已经连接，FALSE表示未连接。
	\sa ConnectToCloud()
	*/
	BOOL IsCloudConnected();

	/*! 连接到某个云设备，连接到云设备后即可和该设备进行交互。 */
	/*!
	\param ip 该云设备的IP地址（已转换成网络字节序）。
	\return 同GetLastError。
	\sa ConnectToCloud()
	*/
	DWORD ConnectToDevice(long ip);

	/*! 断开和某个云设备的连接，注意：该函数不能在回调函数中直接调用。 */
	/*!
	\param ip 该云设备的IP地址（已转换成网络字节序）。
	\return 无。
	\sa ConnectToDevice()
	*/
	VOID DisconnectFromDevice(long ip);

	/*! 断开所有连接，注意：该函数不能在回调函数中直接调用。 */
	/*!
	\param 无。
	\return 无。
	\sa ConnectToCloud() ConnectToDevice()
	*/
	VOID DisconnectAll();

	/*! 发送任务给某个云设备。 */
	/*!
	\param ip 该云设备的IP地址（已转换成网络字节序）。
	\param lpMsg 发送给该设备的消息包。
	\return TRUE表示添加消息到消息队列成功，否则表示失败（可能与该设备未建立连接）。
	\sa
	*/
	BOOL PostTask(long ip, LPCLOUDMSG_HDR lpMsg);

	/*! 调用回调函数。 */
	/*!
	\param dwReason 调用回调函数的原因。
	\param ip 对方设备的IP地址（已转换成网络字节序）。
	\param lpBuffer 具体的参数因回调函数的原因不同而不同。
	\return TRUE表示成功，否则表示失败。
	\sa RegisterCallback()
	*/
	BOOL Callback(DWORD dwReason, long ip, DWORD_PTR lpBuffer);

	/*! 获取某个云设备信息。 */
	/*!
	\param ip 该设备的IP地址（已转换成网络字节序）。
	\param lpszDeviceId 该设备的ID。
	\param lpszDeviceName 该设备的名称。
	\param lpszDeviceType 该设备的类型。
	\return TRUE表示成功，否则表示失败。
	\sa
	*/
	BOOL GetDeviceInformation(long ip, LPTSTR lpszDeviceId, LPTSTR lpszDeviceName, LPTSTR lpszDeviceType);

	/*! 是否和某个云设备建立了连接。 */
	/*!
	\param ip 该设备的IP地址（已转换成网络字节序）。
	\return TRUE表示建立了连接，否则表示未建立连接。
	\sa
	*/
	BOOL IsDeviceConnected(long ip);

	/*! 获取当前连接的设备IP列表。 */
	/*!
	\param ip 该设备的IP地址（已转换成网络字节序）。
	\return 无。
	\sa FreeConnectedDeviceList()
	*/
	VOID GetConnectedDeviceList(long **pList, DWORD &dwCount);

	/*! 释放当前连接的设备IP列表。 */
	/*!
	\param pList IP地址列表。
	\return 无。
	\sa GetConnectedDeviceList()
	*/
	VOID FreeConnectedDeviceList(long *pList);

private:

	CCriticalSection m_cs;
	BOOL m_bConnected;			// 是否已经连接到私有云

	HANDLE m_hUdpListenThread;	// 监听并处理广播消息
	HANDLE m_hTcpListenThread;	// 监听其它云设备的通信请求
	SOCKET m_hUdpListenSocket;
	SOCKET m_hTcpListenSocket;
	HANDLE m_hHeartbeatThread;
	HANDLE m_hTerminateEvent;
	
	LPCLOUD_CALLBACK m_lpCallbackRoutine;
	DWORD_PTR m_lpCallbackContext;

	std::map<long, PRIVATECLOUD_ITEM> m_cDeviceList;

public:

	TCHAR m_szDeviceId[DEVICE_ID_LENGTH];
	TCHAR m_szDeviceName[DEVICE_NAME_LENGTH];
	TCHAR m_szDeviceType[DEVICE_TYPE_LENGTH];

	TCHAR m_szCloudId[CLOUD_ID_LENGTH];
	TCHAR m_szCloudName[CLOUD_NAME_LENGTH];
	TCHAR m_szPassword[64];
};
